package com.neborosoft.lib.utilities;

import android.os.Environment;
import android.support.v4.util.LruCache;
import android.util.Log;
import com.neborosoft.lib.FlyingDogRuntimeException;
import com.neborosoft.lib.Maps;
import com.neborosoft.lib.strings.Strings;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: CM
 * Date: 02.11.12
 * Time: 23:53
 * To change this template use File | Settings | File Templates.
 */
public final class Utilities {
    private static Map<String,Integer> allocationStatistics = new HashMap<String, Integer>();

    public interface Logger{
        void e(String key, String message);
        void d(String key, String message);
        void i(String key, String message);
        void w(String key, String message);
    }

    public static void logException(final Throwable e){
        logException(e, null);
    }

    public static void logException(final Throwable e, String additionalDescription) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        String path = Environment.getExternalStorageDirectory() +
                File.separator +
                "FlyingDog/logs/";
        final File directory = new File(path);
        directory.mkdirs();

        e.printStackTrace(pw);
        String content = pw.toString();

        try {
            File file = new File(directory, e.getClass().getName() + "___" +
                    System.currentTimeMillis() + ".txt");

            new FileOutputStream(file.getAbsolutePath()).close();
            FileWriter fileWriter = new FileWriter(file.getAbsolutePath());
            fileWriter.write(content);

            if(additionalDescription != null && !additionalDescription.isEmpty()){
                fileWriter.write("\n\n");
                fileWriter.write("Description:\n");
                fileWriter.write(additionalDescription);
            }

        } catch (IOException _) {

        }

        Log.e("dermo", content);
    }

    public static void measureExecutionTime(String tag, String name, Runnable runnable){
        long before = System.currentTimeMillis();
        runnable.run();
        long time = System.currentTimeMillis() - before;
        Log.i(tag, name + ": " + time);
    }

    public static String getCurrentStackTrace(){
        return Strings.join(Thread.currentThread().getStackTrace(), "\n");
    }

    public static int[] pushDrawableState(int[] states, int state) {
        int[] result = new int[states.length + 1];
        System.arraycopy(states, 0, result, 0, states.length);
        result[states.length] = state;

        return result;
    }

    public static int[] popDrawableState(int[] states) {
        int[] result = new int[states.length - 1];
        System.arraycopy(states, 0, result, 0, states.length - 1);

        return result;
    }

    public static interface ParamTransformer{
        Object transform(String paramName, Object value);
    }

    public static Map<String, Object> objectToPropertyMap(Object object, ParamTransformer paramTransformer) {
        Map<String, Object> result = new HashMap<String, Object>();
        Class objectClass = object.getClass();
        Field[] fields = objectClass.getFields();
        try {
            for (Field field : fields) {
                Object value = field.get(object);
                String key = field.getName();

                if(paramTransformer != null){
                    value = paramTransformer.transform(key, value);
                }

                if (value == null) {
                    continue;
                }

                result.put(key, value);
            }
        } catch (IllegalAccessException e) {
            Utilities.logException(e);
        }

        return result;
    }

    public static Map<String, Object> objectToPropertyMap(Object object) {
        return objectToPropertyMap(object,null);
    }

    public static <K, V> Map<K, V> toMap(final LruCache<K, V> cache) {
        return new Map<K, V>() {
            @Override
            public void clear() {
                throw new UnsupportedOperationException();
            }

            @Override
            public boolean containsKey(Object key) {
                throw new UnsupportedOperationException();
            }

            @Override
            public boolean containsValue(Object value) {
                throw new UnsupportedOperationException();
            }

            @Override
            public Set<Entry<K, V>> entrySet() {
                throw new UnsupportedOperationException();
            }

            @Override
            public V get(Object key) {
                return cache.get((K) key);
            }

            @Override
            public boolean isEmpty() {
                return cache.size() == 0;
            }

            @Override
            public Set<K> keySet() {
                throw new UnsupportedOperationException();
            }

            @Override
            public V put(K key, V value) {
                return cache.put(key, value);
            }

            @Override
            public void putAll(Map<? extends K, ? extends V> map) {
                for (Map.Entry<? extends K, ? extends V> i : map.entrySet()) {
                    cache.put(i.getKey(), i.getValue());
                }
            }

            @Override
            public V remove(Object key) {
                return cache.remove((K) key);
            }

            @Override
            public int size() {
                return cache.size();
            }

            @Override
            public Collection<V> values() {
                throw new UnsupportedOperationException();
            }
        };
    }

    public static <T> T createObjectOfClass(Class<T> type, Object... params) throws Throwable {
        Constructor[] constructors = type.getConstructors();
        for (Constructor constructor : constructors) {
            try {
                T object = (T) constructor.newInstance(params);
                return object;
            } catch (InstantiationException e) {

            } catch (IllegalAccessException e) {

            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        }
        throw new FlyingDogRuntimeException("no appropriate constructor available");
    }

    public static void debugLogAllocation(Object object){
        Class objectClass = object.getClass();
        String className = objectClass.getName();
        Maps.incrementMapValue(allocationStatistics, className);
    }

    public static void debugLogDeallocate(Object object){
        Class objectClass = object.getClass();
        String className = objectClass.getName();
        Maps.decrementMapUnsignedValue(allocationStatistics, className);
    }

    public static Object[] objectToPropertiesArray(Object object){
        Class objectClass = object.getClass();
        Field[] fields = objectClass.getFields();
        Object[] result = new Object[fields.length];

        for(int i = 0; i < fields.length;){
            Field field = fields[i];
            if(Modifier.isTransient(field.getModifiers())){
                continue;
            }

            field.setAccessible(true);
            try {
                result[i] = field.get(object);
            } catch (IllegalAccessException e) {
                Utilities.logException(e);
            }

            i++;
        }

        return result;
    }
}
